Skip to content

feat: Adding Expiration Column to API Tokens#2421

Open
RaymondLaubert wants to merge 2 commits intomainfrom
BED-7449/ApiTokens-ExpirationColumn
Open

feat: Adding Expiration Column to API Tokens#2421
RaymondLaubert wants to merge 2 commits intomainfrom
BED-7449/ApiTokens-ExpirationColumn

Conversation

@RaymondLaubert
Copy link
Contributor

@RaymondLaubert RaymondLaubert commented Feb 25, 2026

Description

Updated the auth_token table to include a column for the expiration date of the token.

Motivation and Context

Resolves BED-7449

This change adds the expiration column to the auth_tokens table allowing an expiration date to be set for an API token. This further expands on allowing API tokens to be set to expire; which clients have requested.

How Has This Been Tested?

Must be tested further. Placeholder for now.

Screenshots (optional):

Types of changes

  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Database Migrations

Checklist:

Summary by CodeRabbit

  • New Features
    • Authentication tokens now include expiration date and time information that displays in token details.
    • Added support for sorting authentication tokens by their expiration date.
    • Added support for filtering authentication tokens based on expiration date.

@RaymondLaubert RaymondLaubert self-assigned this Feb 25, 2026
@RaymondLaubert RaymondLaubert added enhancement New feature or request api A pull request containing changes affecting the API code. labels Feb 25, 2026
@github-actions
Copy link

github-actions bot commented Feb 25, 2026

Howdy! Thank you for opening this pull request 🙇

Your title is formatted correctly but we did not find a matching issue reference.
Please verify that the reference is correct and available in Jira or GitHub issues.

Details:

No issue reference found in the title.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 25, 2026

📝 Walkthrough

Walkthrough

Introduces token expiration support by adding an expires_at column to the auth_tokens database table and extending the AuthToken model with an Expiration field. Updates sorting and filtering capabilities to include the new expiration timestamp.

Changes

Cohort / File(s) Summary
Database Migration
cmd/api/src/database/migration/migrations/v8.7.0.sql
Adds expires_at column (timestamp with time zone) to auth_tokens table.
Auth Model Updates
cmd/api/src/model/auth.go
Adds Expiration field to AuthToken struct, updates StripKey() to preserve expiration, and extends IsSortable() and ValidFilters() to include expires_at.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested reviewers

  • superlinkx
  • elikmiller
  • irshadaj
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding an expiration column to API tokens. It is specific, descriptive, and directly reflects the primary change in the changeset.
Description check ✅ Passed The description adequately covers required sections: a clear description of changes, motivation/context with issue reference (BED-7449), types of changes selected, and all checklist items marked complete. The testing section notes it as a placeholder for further testing, which is acknowledged.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch BED-7449/ApiTokens-ExpirationColumn

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
cmd/api/src/database/migration/migrations/v8.7.0.sql (1)

150-152: Consider indexing auth_tokens.expires_at for the newly added query path.

Line 152 adds expires_at, and token sorting/filtering now includes "expires_at" in cmd/api/src/model/auth.go (Line 191, Line 209). On larger datasets, this can degrade to full scans/sorts without an index.

💡 Suggested migration addition
 ALTER TABLE auth_tokens
 ADD COLUMN IF NOT EXISTS expires_at timestamp with time zone;
+
+CREATE INDEX IF NOT EXISTS idx_auth_tokens_expires_at
+    ON auth_tokens (expires_at);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/api/src/database/migration/migrations/v8.7.0.sql` around lines 150 - 152,
Add an index on the new auth_tokens.expires_at column to avoid full-table scans
during sorting/filtering; update the v8.7.0 migration to CREATE INDEX
CONCURRENTLY IF NOT EXISTS on auth_tokens(expires_at) (or a partial index if you
only query non-null/unrevoked tokens) so code paths that reference "expires_at"
in the auth model use the index.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/api/src/model/auth.go`:
- Line 154: The Expiration field in the auth model is missing an explicit GORM
column mapping which causes GORM to target "expiration" instead of the DB column
"expires_at"; update the struct field Expiration (in cmd/api/src/model/auth.go)
to include a gorm tag mapping to column:expires_at (e.g., add
gorm:"column:expires_at" alongside the existing json tag) so reads/writes use
the correct database column.

---

Nitpick comments:
In `@cmd/api/src/database/migration/migrations/v8.7.0.sql`:
- Around line 150-152: Add an index on the new auth_tokens.expires_at column to
avoid full-table scans during sorting/filtering; update the v8.7.0 migration to
CREATE INDEX CONCURRENTLY IF NOT EXISTS on auth_tokens(expires_at) (or a partial
index if you only query non-null/unrevoked tokens) so code paths that reference
"expires_at" in the auth model use the index.

ℹ️ Review info

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a13c695 and ea8fa9e.

📒 Files selected for processing (2)
  • cmd/api/src/database/migration/migrations/v8.7.0.sql
  • cmd/api/src/model/auth.go

Key string `json:"key,omitempty"`
HmacMethod string `json:"hmac_method"`
LastAccess time.Time `json:"last_access"`
Expiration null.Time `json:"expires_at"`
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== AuthToken field/tag check =="
rg -n -C2 'type AuthToken struct|Expiration|null.Time|expires_at|gorm:"column:expires_at"' cmd/api/src/model/auth.go

echo
echo "== GORM naming strategy check (repo-wide) =="
rg -n -C3 'gorm\.Config|NamingStrategy|schema\.NamingStrategy|NameReplacer|ColumnName' --type=go cmd/api/src

echo
echo "== Column name references for auth_tokens =="
rg -n -C2 '\bauth_tokens\b|\bexpires_at\b|\bexpiration\b' cmd/api/src

Repository: SpecterOps/BloodHound

Length of output: 14522


Add explicit GORM column mapping to prevent mismatch with database schema.

At line 154, the Expiration field lacks a gorm:"column:expires_at" tag. GORM's default naming convention converts Expiration to expiration, but the migration creates the column as expires_at. This mismatch will cause reads and writes on this field to target the wrong column.

Fix
-	Expiration null.Time	 `json:"expires_at"`
+	Expiration null.Time   `json:"expires_at" gorm:"column:expires_at"`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Expiration null.Time `json:"expires_at"`
Expiration null.Time `json:"expires_at" gorm:"column:expires_at"`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/api/src/model/auth.go` at line 154, The Expiration field in the auth
model is missing an explicit GORM column mapping which causes GORM to target
"expiration" instead of the DB column "expires_at"; update the struct field
Expiration (in cmd/api/src/model/auth.go) to include a gorm tag mapping to
column:expires_at (e.g., add gorm:"column:expires_at" alongside the existing
json tag) so reads/writes use the correct database column.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other way to fix this is to use the same name: ExpiresAt instead of Expiration

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@superlinkx You're absolutely right! Renaming the field to ExpiresAt is a better solution. It follows the existing convention in the codebase (e.g., AuthSecret.ExpiresAt) and is more idiomatic. GORM will automatically convert ExpiresAt to expires_at via snake_case, matching the database column.

This approach is cleaner and more consistent with the rest of the codebase. The StripKey() method would also need updating to use ExpiresAt instead of Expiration:

func (s AuthToken) StripKey() AuthToken {
	return AuthToken{
		UserID:     s.UserID,
		ClientID:   s.ClientID,
		Key:        "",
		HmacMethod: s.HmacMethod,
		LastAccess: s.LastAccess,
		Unique:     s.Unique,
		Name:       s.Name,
		ExpiresAt:  s.ExpiresAt,
	}
}

✏️ Learnings added
Learnt from: superlinkx
Repo: SpecterOps/BloodHound PR: 2421
File: cmd/api/src/model/auth.go:154-154
Timestamp: 2026-02-25T23:53:13.603Z
Learning: In the BloodHound codebase (SpecterOps/BloodHound), time-based expiration fields should be named `ExpiresAt` (not `Expiration`) to follow existing conventions (e.g., `AuthSecret.ExpiresAt`) and to allow GORM's automatic snake_case conversion to correctly map to `expires_at` database columns without requiring explicit `gorm:"column:expires_at"` tags.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: brandonshearin
Repo: SpecterOps/BloodHound PR: 2383
File: cmd/api/src/database/kind.go:61-69
Timestamp: 2026-02-25T20:04:47.906Z
Learning: In the BloodHound codebase (SpecterOps/BloodHound), GORM v1.25.x is used. For Raw queries with IN clauses that take slices, the correct syntax is `IN (?)` with parentheses (e.g., `WHERE id IN (?)`), not `IN ?`. This pattern is used consistently across the codebase in files like cmd/api/src/database/kind.go and cmd/api/src/database/sourcekinds.go.

Learnt from: superlinkx
Repo: SpecterOps/BloodHound PR: 1820
File: packages/go/stbernard/command/audit/audit.go:133-136
Timestamp: 2025-08-26T21:25:33.480Z
Learning: superlinkx prefers to receive feedback and identify issues as early as possible in the review process rather than later.

Learnt from: mvlipka
Repo: SpecterOps/BloodHound PR: 2382
File: cmd/api/src/api/v2/ad_related_entity.go:44-57
Timestamp: 2026-02-23T17:06:23.991Z
Learning: In BloodHound, AD nodes are represented by a base kind (ad.Entity) plus specific kinds (ad.User, ad.Computer, ad.Group, etc.), and Azure nodes by az.Entity. The query.KindIn function matches if a node has any of the specified kinds, so using the base kind (ad.Entity) will correctly include all AD nodes regardless of their specific subtype. During reviews, ensure code that filters or queries graph nodes via KindIn uses the base kind to capture all inherited subkinds for AD nodes, and similarly recognizes base kinds for Azure nodes. This design pattern should be consistently respected across files that model or query graph schema kinds.

Copy link
Contributor

@superlinkx superlinkx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a quick sanity check plus CodeRabbit's issue, otherwise this looks correct for the ticket


-- Update the 'auth_tokens' Table adding Expiration Column
ALTER TABLE auth_tokens
ADD COLUMN IF NOT EXISTS expires_at timestamp with time zone; No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to treat this as a nullable field, but this is not creating it as nullable

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api A pull request containing changes affecting the API code. enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants